﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Text;
using System.Timers;
using System.Windows.Forms;

namespace VisualCryptography
{
    public partial class MainForm : Form
    {
        private enum SimulationState
        {
            NONE,
            EXPORTING,
            IMPORTING
        }

        #region Pixel Translation Matrices

        private static byte[][][] BlackPixelTranslation1 = new byte[][][]
        {
            new byte[][]
            {
                new byte[]{1, 0},
                new byte[]{1, 0}
            },

            new byte[][]
            {
                new byte[]{0, 1},
                new byte[]{0, 1}
            }
        };

        private static byte[][][] BlackPixelTranslation2 = new byte[][][]
        {
            new byte[][]
            {
                new byte[]{0, 1},
                new byte[]{0, 1}
            },

            new byte[][]
            {
                new byte[]{1, 0},
                new byte[]{1, 0}
            }
        };

        private static byte[][][] WhitePixelTranslation1 = new byte[][][]
        {
            new byte[][]
            {
                new byte[]{1, 0},
                new byte[]{1, 0}
            },

            new byte[][]
            {
                new byte[]{0, 1},
                new byte[]{0, 1}
            }
        };

        private static byte[][][] WhitePixelTranslation2 = new byte[][][]
        {
            new byte[][]
            {
                new byte[]{1, 0},
                new byte[]{1, 0}
            },

            new byte[][]
            {
                new byte[]{0, 1},
                new byte[]{0, 1}
            }
        };

        #endregion

        private static Random random = new Random();

        private SimulationState simulationState;

        private Point sharePosition1;
        private Point sharePosition2;

        private int shareWidth;
        private int shareHeight;

        private int simulationTimer;

        private Bitmap picture;
        private Bitmap share1;
        private Bitmap share2;

        private string path;

        public MainForm()
        {
            InitializeComponent();

            System.Timers.Timer PointerUpdater = new System.Timers.Timer();

            PointerUpdater.Elapsed += new ElapsedEventHandler(UpdateSimulation);
            PointerUpdater.Interval = 40;
            PointerUpdater.Enabled = true;

            this.exportButton.Enabled = false;
        }

        private void openButton_Click(object sender, EventArgs e)
        {
            OpenFileDialog dialog = new OpenFileDialog();

            dialog.Filter = "All Supported Formats|*.bmp; *.gif; *.jpeg; *.jpg; *.png; *.tiff";

            // Pops up a dialog so the user can select the picture to be open.
            if (dialog.ShowDialog() == DialogResult.OK)
            {
                simulationState = SimulationState.NONE;

                picture = (Bitmap)Bitmap.FromFile(path = dialog.FileName);

                // Attempts to convert the picture to black and white in case it's not.
                bool wasConverted = convertBitmapToBlackAndWhite(picture);

                // Asks for user confirmation in order to proceed with the converted picture in case it had to be converted.
                if (wasConverted == false || MessageBox.Show("The picture you have opened is not in black and white. Would you like to convert it?", this.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                {
                    simulationState = SimulationState.NONE;

                    // Displays the opened and checked/converted picture.
                    this.pictureBox.Image = picture;

                    // Enables the export button.
                    this.exportButton.Enabled = true;
                }

                else
                {
                    this.pictureBox.Image = Bitmap.FromFile(path);

                    // Disables the export button.
                    this.exportButton.Enabled = false;
                }
            }
        }

        private void exportButton_Click(object sender, EventArgs e)
        {
            // Encrypts the picture dividing it in two pieces.
            Bitmap share1;
            Bitmap share2;

            encryptBitmap(picture, out share1, out share2);

            // Determines the path for the shares to be saved.
            FileInfo fileInfo = new FileInfo(path);

            string pathInfo = fileInfo.FullName.Substring(0, fileInfo.FullName.IndexOf(fileInfo.Extension));

            // Saves the two shares.
            share1.Save(pathInfo + ".share1.bmp", ImageFormat.Bmp);
            share2.Save(pathInfo + ".share2.bmp", ImageFormat.Bmp);

            // Resizes the shares.
            share1 = resizeBitmap(share1);
            share2 = resizeBitmap(share2);

            share1.MakeTransparent(Color.White);
            share2.MakeTransparent(Color.White);

            shareWidth = share1.Width;
            shareHeight = share1.Height;

            this.share1 = share1;
            this.share2 = share2;

            this.pictureBox.Image = null;

            // Prepares the simulation.
            sharePosition1 = sharePosition2 = new Point((this.pictureBox.Width / 2) - (shareWidth / 2), (this.pictureBox.Height / 2) - (shareHeight / 2));

            simulationTimer = 0;

            simulationState = SimulationState.EXPORTING;

            // Disables the export button.
            this.exportButton.Enabled = false;
        }

        private void importButton_Click(object sender, EventArgs e)
        {
            OpenFileDialog dialog = new OpenFileDialog();

            dialog.Filter = "Bitmap|*.bmp";

            // Pops up a dialog so the user can select share 1 to be open.
            if (dialog.ShowDialog() == DialogResult.OK)
            {
                share1 = (Bitmap)Bitmap.FromFile(dialog.FileName);

                // Pops up a dialog so the user can select share 2 to be open.
                if (dialog.ShowDialog() == DialogResult.OK)
                {
                    share2 = (Bitmap)Bitmap.FromFile(dialog.FileName);

                    // Resizes the shares.
                    share1 = resizeBitmap(share1);
                    share2 = resizeBitmap(share2);

                    share1.MakeTransparent(Color.White);
                    share2.MakeTransparent(Color.White);

                    shareWidth = share1.Width;
                    shareHeight = share1.Height;

                    this.pictureBox.Image = null;

                    // Prepares the simulation
                    sharePosition1 = new Point((this.pictureBox.Width / 2) - (int)(shareWidth * 0.75), (this.pictureBox.Height / 2) - (shareHeight / 2));
                    sharePosition2 = new Point((this.pictureBox.Width / 2) - (int)(shareWidth * 0.25), (this.pictureBox.Height / 2) - (shareHeight / 2));

                    simulationTimer = 0;

                    simulationState = SimulationState.IMPORTING;

                    // Disables the export button.
                    this.exportButton.Enabled = false;
                }
            }
        }

        private void pictureBox_Paint(object sender, PaintEventArgs e)
        {
            if (simulationState != SimulationState.NONE)
            {
                e.Graphics.DrawImage(share1, sharePosition1);
                e.Graphics.DrawImage(share2, sharePosition2);
            }
        }

        private void UpdateSimulation(object source, ElapsedEventArgs e)
        {
            switch (simulationState)
            {
                case SimulationState.EXPORTING:

                    if (simulationTimer >= 25)
                    {
                        int limit1 = (this.pictureBox.Width / 2) - (int)(shareWidth * 0.75);
                        int limit2 = (this.pictureBox.Width / 2) - (int)(shareWidth * 0.25);

                        sharePosition1.X = Math.Max(sharePosition1.X - 4, limit1);
                        sharePosition2.X = Math.Min(sharePosition2.X + 4, limit2);

                        if (sharePosition1.X == limit1 && sharePosition2.X == limit2)
                        {
                            simulationState = SimulationState.NONE;
                        }
                    }

                    simulationTimer++;

                    this.pictureBox.Invalidate();

                    if (simulationState == SimulationState.NONE)
                    {
                        this.pictureBox.Image = picture;
                    }

                    break;

                case SimulationState.IMPORTING:

                    if (simulationTimer >= 25)
                    {
                        int limit3 = (this.pictureBox.Width / 2) - (shareWidth / 2);

                        sharePosition1.X = Math.Min(sharePosition1.X + 4, limit3);
                        sharePosition2.X = Math.Max(sharePosition2.X - 4, limit3);
                    }

                    simulationTimer++;

                    this.pictureBox.Invalidate();

                    break;
            }
        }

        private static void encryptBitmap(Bitmap bitmap, out Bitmap encryptedBitmap1, out Bitmap encryptedBitmap2)
        {
            encryptedBitmap1 = new Bitmap(bitmap.Width * 2, bitmap.Height * 2);
            encryptedBitmap2 = new Bitmap(bitmap.Width * 2, bitmap.Height * 2);

            BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);

            BitmapData encryptedBitmapData1 = encryptedBitmap1.LockBits(new Rectangle(0, 0, encryptedBitmap1.Width, encryptedBitmap1.Height), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);
            BitmapData encryptedBitmapData2 = encryptedBitmap2.LockBits(new Rectangle(0, 0, encryptedBitmap2.Width, encryptedBitmap2.Height), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);

            IntPtr scanZero1 = bitmapData.Scan0;

            IntPtr scanZero2 = encryptedBitmapData1.Scan0;
            IntPtr scanZero3 = encryptedBitmapData2.Scan0;

            unsafe
            {
                byte* ptr1 = (byte*)(void*)scanZero1;
                byte* ptr2 = (byte*)(void*)scanZero2;
                byte* ptr3 = (byte*)(void*)scanZero3;

                for (int y = 0; y < bitmap.Height; y++)
                {
                    for (int x = 0; x < bitmap.Width; x++)
                    {
                        int set = random.Next(BlackPixelTranslation1.Length);

                        for (int i = 0; i < BlackPixelTranslation1[0].Length; i++)
                        {
                            for (int j = 0; j < BlackPixelTranslation1[0][0].Length; j++)
                            {
                                for (int k = 0; k < 3; k++)
                                {
                                    // Black Pixel.
                                    if (ptr1[(y * bitmapData.Stride) + (x * 3)] == 0)
                                    {
                                        ptr2[(((y * 2) + i) * encryptedBitmapData1.Stride) + (((x * 2) + j) * 3) + k] = (byte)(BlackPixelTranslation1[set][i][j] * 255);
                                        ptr3[(((y * 2) + i) * encryptedBitmapData2.Stride) + (((x * 2) + j) * 3) + k] = (byte)(BlackPixelTranslation2[set][i][j] * 255);
                                    }

                                    // White Pixel.
                                    else
                                    {
                                        ptr2[(((y * 2) + i) * encryptedBitmapData1.Stride) + (((x * 2) + j) * 3) + k] = (byte)(WhitePixelTranslation1[set][i][j] * 255);
                                        ptr3[(((y * 2) + i) * encryptedBitmapData2.Stride) + (((x * 2) + j) * 3) + k] = (byte)(WhitePixelTranslation2[set][i][j] * 255);
                                    }
                                }
                            }
                        }
                    }
                }
            }

            encryptedBitmap1.UnlockBits(encryptedBitmapData1);
            encryptedBitmap2.UnlockBits(encryptedBitmapData2);

            bitmap.UnlockBits(bitmapData);
        }

        private static Bitmap resizeBitmap(Bitmap bitmap)
        {
            Bitmap resbmp = new Bitmap(bitmap.Width / 2, bitmap.Height / 2);

            BitmapData bitmapData1 = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
            BitmapData bitmapData2 = resbmp.LockBits(new Rectangle(0, 0, resbmp.Width, resbmp.Height), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);

            IntPtr scanZero1 = bitmapData1.Scan0;
            IntPtr scanZero2 = bitmapData2.Scan0;

            unsafe
            {
                byte* ptr1 = (byte*)(void*)scanZero1;
                byte* ptr2 = (byte*)(void*)scanZero2;

                for (int y = 0; y < bitmap.Height; y++)
                {
                    for (int x = 0; x < bitmap.Width; x++)
                    {
                        if (y % 2 == 0 && x % 2 == 0)
                        {
                            for (int i = 0; i < 3; i++)
                            {
                                ptr2[((y / 2) * bitmapData2.Stride) + ((x / 2) * 3) + i] = ptr1[(y * bitmapData1.Stride) + (x * 3) + i];
                            }
                        }
                    }
                }
            }

            resbmp.UnlockBits(bitmapData2);
            bitmap.UnlockBits(bitmapData1);

            return resbmp;
        }

        private static bool convertBitmapToBlackAndWhite(Bitmap bitmap)
        {
            BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

            IntPtr scanZero = bitmapData.Scan0;

            bool modified = false;

            unsafe
            {
                byte* ptr = (byte*)(void*)scanZero;

                for (int y = 0; y < bitmap.Height; y++)
                {
                    for (int x = 0; x < bitmap.Width; x++)
                    {
                        int r = (y * bitmapData.Stride) + (x * 3);
                        int g = (y * bitmapData.Stride) + (x * 3) + 1;
                        int b = (y * bitmapData.Stride) + (x * 3) + 2;

                        if (ptr[r] != 0 || ptr[r] != 255 || ptr[r] != ptr[g] || ptr[r] != ptr[b])
                        {
                            byte value = (byte)(Math.Round(((double)(ptr[r] + ptr[g] + ptr[b])) / (255 * 3)) * 255);

                            ptr[r] = value;
                            ptr[g] = value;
                            ptr[b] = value;

                            modified = true;
                        }
                    }
                }
            }

            bitmap.UnlockBits(bitmapData);

            return modified;
        }
    }
}
